package lichunUtil;

import java.util.Arrays;
import java.util.Random;

/**
 * A simple tool for some kind of random
 * 
 * @author lichunyang
 */
public class GenRandom {
	static public class a {
		static public void show() {
			System.out.println("hello");;
		}
	}
	
	private Random ran;
	public GenRandom (long seed) {
		this.ran = new Random(seed);
	}
	public GenRandom (Random ran) {
		this.ran = ran;
	}
	
	/**
	 * Generates a random sequence.
	 * 
	 * @param offset
	 *            the offset subscript of the sequence
	 * @param count
	 *            the size of the sequence
	 * @return a random sequence
	 */
	public int[] genRandomSequence_seed(int offset, int count) {
		if (count < 0) {
			System.err.println("invalid parameter.  count:" + count);
			return null;
		}
		if (count == 0) {
			//System.err.println("count is zero");
			return new int[0];
		}
		if (count == 1)
			return new int[] { offset };

		int[] sel = new int[count];
		for (int i = 0; i < sel.length; i++) {
			sel[i] = i;
		}

		int[] res = new int[count];
		for (int i = 0; i < res.length; i++) {
			int index = (int) (ran.nextDouble() * (res.length - i));
			res[i] = sel[index] + offset;
			sel[index] = sel[res.length - i - 1];
		}
		return res;
	}
	
	/**
	 * Generates a random sequence.
	 * 
	 * @param count
	 *            the size of the sequence
	 * @return a random sequence
	 */
	public int[] genRandomSequence_seed(int count) {
		if (count < 0) {
			System.err.println("invalid parameter.  count:" + count);
			return null;
		}
		if (count == 0) {
			//System.err.println("count is zero");
			return new int[0];
		}
		if (count == 1)
			return new int[] { 0 };

		return genRandomSequence_seed(0, count);
	}
	
	/**
	 * Random sample an sub set from original set.
	 * 
	 * @param oriTotal
	 *            original set size
	 * @param sampleTotal
	 *            sample set size
	 * @return an array of the selected elements' subscripts
	 */
	public int[] genRandomSample_seed (int oriTotal, int sampleTotal) {
		if (oriTotal < sampleTotal || oriTotal < 0 || sampleTotal < 0) {
			System.err.println("invalid parameter.  oriTotal:" + oriTotal
					+ " sampleTotal:" + sampleTotal);
			return null;
		}
		if (sampleTotal == 0) {
			//System.err.println("sampleTotal is zero");
			return new int[0];
		}

		int[] res = new int[sampleTotal];
		double has = 0;
		for (int i = 0; i < oriTotal; i++) {
			if (ran.nextDouble() < (sampleTotal - has) / (oriTotal - i)) {
				res[(int) has] = i;
				has++;
			}
		}
		return res;
	}
	
	/**
	 * Randomly select an element according to the given weighting array.
	 * 
	 * @param weighting
	 *            an array of each element's weighting
	 * @return the subscript of the randomly selected element according to the
	 *         weighting array
	 */
	public int genRandomID_seed (double[] weighting) {
		if (weighting == null || weighting.length == 0) {
			String msg = weighting == null ? "null" : "zero length";
			System.err.println("invalid parameter.  weighting is " + msg);
			return -1;
		}
		if (weighting.length == 1)
			return 0;

		double[] temp = new double[weighting.length];
		assert weighting[0] >= 0;
		temp[0] = weighting[0];

		for (int i = 1; i < weighting.length; i++) {
			assert weighting[i] >= 0;
			temp[i] = weighting[i] + temp[i - 1];
		}
		double ran = this.ran.nextDouble() * temp[temp.length - 1];

		int res = Arrays.binarySearch(temp, ran);
		if (res > 0)
			return res;
		return Math.abs(res) - 1;
	}
	
	/**
	 * Randomly select an element according to the given weighting array.
	 * 
	 * @param weighting
	 *            an array of each element's weighting
	 * @return the subscript of the randomly selected element according to the
	 *         weighting array
	 */
	public int genRandomID_seed (Number[] weighting) {
		if (weighting == null || weighting.length == 0) {
			String msg = weighting == null ? "null" : "zero length";
			System.err.println("invalid parameter.  weighting is " + msg);
			return -1;
		}
		if (weighting.length == 1)
			return 0;

		double[] temp = new double[weighting.length];
		assert weighting[0].doubleValue() >= 0;
		temp[0] = weighting[0].doubleValue();

		for (int i = 1; i < weighting.length; i++) {
			assert weighting[i].doubleValue() >= 0;
			temp[i] = weighting[i].doubleValue() + temp[i - 1];
		}
		double ran = this.ran.nextDouble() * temp[temp.length - 1];

		int res = Arrays.binarySearch(temp, ran);
		if (res > 0)
			return res;
		return Math.abs(res) - 1;
	}

	
	/**
	 * Generates a random sequence.
	 * 
	 * @param offset
	 *            the offset subscript of the sequence
	 * @param count
	 *            the size of the sequence
	 * @return a random sequence
	 */
	static public int[] genRandomSequence(int offset, int count) {
		if (count < 0) {
			System.err.println("invalid parameter.  count:" + count);
			return null;
		}
		if (count == 0) {
			//System.err.println("count is zero");
			return new int[0];
		}
		if (count == 1)
			return new int[] { offset };

		int[] sel = new int[count];
		for (int i = 0; i < sel.length; i++) {
			sel[i] = i;
		}

		int[] res = new int[count];
		for (int i = 0; i < res.length; i++) {
			int index = (int) (Math.random() * (res.length - i));
			res[i] = sel[index] + offset;
			sel[index] = sel[res.length - i - 1];
		}
		return res;
	}

	/**
	 * Generates a random sequence.
	 * 
	 * @param count
	 *            the size of the sequence
	 * @return a random sequence
	 */
	static public int[] genRandomSequence(int count) {
		if (count < 0) {
			System.err.println("invalid parameter.  count:" + count);
			return null;
		}
		if (count == 0) {
			//System.err.println("count is zero");
			return new int[0];
		}
		if (count == 1)
			return new int[] { 0 };

		return genRandomSequence(0, count);
	}

	/**
	 * Random sample an sub set from original set.
	 * 
	 * @param oriTotal
	 *            original set size
	 * @param sampleTotal
	 *            sample set size
	 * @return an array of the selected elements' subscripts
	 */
	static public int[] genRandomSample(int oriTotal, int sampleTotal) {
		if (oriTotal < sampleTotal || oriTotal < 0 || sampleTotal < 0) {
			System.err.println("invalid parameter.  oriTotal:" + oriTotal
					+ " sampleTotal:" + sampleTotal);
			return null;
		}
		if (sampleTotal == 0) {
			//System.err.println("sampleTotal is zero");
			return new int[0];
		}

		int[] res = new int[sampleTotal];
		double has = 0;
		for (int i = 0; i < oriTotal; i++) {
			if (Math.random() < (sampleTotal - has) / (oriTotal - i)) {
				res[(int) has] = i;
				has++;
			}
		}
		return res;
	}

	/**
	 * Randomly select an element according to the given weighting array.
	 * 
	 * @param weighting
	 *            an array of each element's weighting
	 * @return the subscript of the randomly selected element according to the
	 *         weighting array
	 */
	static public int genRandomID(double[] weighting) {
		if (weighting == null || weighting.length == 0) {
			String msg = weighting == null ? "null" : "zero length";
			System.err.println("invalid parameter.  weighting is " + msg);
			return -1;
		}
		if (weighting.length == 1)
			return 0;

		double[] temp = new double[weighting.length];
		assert weighting[0] >= 0;
		temp[0] = weighting[0];

		for (int i = 1; i < weighting.length; i++) {
			assert weighting[i] >= 0;
			temp[i] = weighting[i] + temp[i - 1];
		}
		double ran = Math.random() * temp[temp.length - 1];

		int res = Arrays.binarySearch(temp, ran);
		if (res > 0)
			return res;
		return Math.abs(res) - 1;
	}

	/**
	 * Randomly select an element according to the given weighting array.
	 * 
	 * @param weighting
	 *            an array of each element's weighting
	 * @return the subscript of the randomly selected element according to the
	 *         weighting array
	 */
	static public int genRandomID(Number[] weighting) {
		if (weighting == null || weighting.length == 0) {
			String msg = weighting == null ? "null" : "zero length";
			System.err.println("invalid parameter.  weighting is " + msg);
			return -1;
		}
		if (weighting.length == 1)
			return 0;

		double[] temp = new double[weighting.length];
		assert weighting[0].doubleValue() >= 0;
		temp[0] = weighting[0].doubleValue();

		for (int i = 1; i < weighting.length; i++) {
			assert weighting[i].doubleValue() >= 0;
			temp[i] = weighting[i].doubleValue() + temp[i - 1];
		}
		double ran = Math.random() * temp[temp.length - 1];

		int res = Arrays.binarySearch(temp, ran);
		if (res > 0)
			return res;
		return Math.abs(res) - 1;
	}

	static public void selftest() {
		double[] prob = new double[4];
		prob[0] = 0.1;
		prob[1] = 0.1;
		int[] res = new int[10];
		for (int i = 0; i < 1000000; i++) {
			int x = genRandomID(prob);
			res[x]++;
		}
		for (int i = 0; i < res.length; i++) {
			System.out.printf("%d\t%d%n", i, res[i]);
		}
	}
}
